#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>

#include <linux/kernel.h>	/* printk() */
#include <linux/slab.h>		/* kmalloc() */
#include <linux/fs.h>		/* everything... */
#include <linux/errno.h>	/* error codes */
#include <linux/types.h>	/* size_t */
#include <linux/proc_fs.h>
#include <linux/fcntl.h>	/* O_ACCMODE */
#include <linux/seq_file.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>
#include <linux/sched.h> /* current and everything */
//#include <linux/delay.h>  /* udelay */

#include <asm/system.h>		/* cli(), *_flags */
#include <asm/uaccess.h>	/* copy_*_user */
#include <asm/atomic.h>

#include "IO_irq.h"		/* local definitions */

/*
 * Our parameters which can be set at load time.
 */
int IO_irq_major =   0;
int IO_irq_minor =   0;

module_param(IO_irq_major, int, S_IRUGO);
module_param(IO_irq_minor, int, S_IRUGO);



struct IO_irq_dev *IO_irq_devices;	/* allocated in scull_init_module */
static atomic_t IO_irq_available = ATOMIC_INIT(1);
static spinlock_t IO_irq_lock = SPIN_LOCK_UNLOCKED;
static DECLARE_WAIT_QUEUE_HEAD(IO_irq_wait);
static unsigned long prevjiffies = 0;
static unsigned int prevkey = 0;
static struct IO_irq_key key1;
static struct IO_irq_key key2;
static struct IO_irq_key key3;
static struct IO_irq_key key4;
struct workqueue_struct *tekkamanwork;
static struct delayed_work irq_work_delay;
static struct work_struct irq_work;
static struct tasklet_struct keytask;
struct kfifo *tekkamanfifo;
static spinlock_t tekkamanlock= SPIN_LOCK_UNLOCKED;
static unsigned char *tekkamantmp;
static unsigned char *tekkamanbuf ;

/*key1_tasklet*/
void key1_tasklet(unsigned long arg)
{
	struct IO_irq_key *data = (struct IO_irq_key *)arg;
	unsigned long j=0;

	printk("\n**************key1_tasklet_start*****************\n");
	j = jiffies;
	printk("time:%08lx  delta:%3li     inirq:%i    pid:%3i   cpu:%i   command:%s\n",
			     j, j - data->prevjiffies, in_interrupt() ? 1 : 0,
			     current->pid, smp_processor_id(), current->comm);
	printk("\n**************key1_tasklet_end*****************\n");

}
/*key2-3_work_fn*/
void irq_work_delay_fn(void *arg)
{
	struct IO_irq_key *data = &key2;
	unsigned long j=0;

	printk("\n**************key2_workqueue_start*****************\n");
	j = jiffies;
	printk("time:%08lx  delta:%3li     inirq:%i    pid:%3i   cpu:%i   command:%s\n",
			     j, j - data->prevjiffies, in_interrupt() ? 1 : 0,
			     current->pid, smp_processor_id(), current->comm);
	printk("\n**************key2_workqueu_end*****************\n");

}

void irq_work_fn(void *arg)
{
	struct IO_irq_key *data = &key3;
	unsigned long j=0;

	printk("\n**************key3_workqueue_start*****************\n");
	j = jiffies;
	printk("time:%08lx  delta:%3li     inirq:%i    pid:%3i   cpu:%i   command:%s\n",
			     j, j - data->prevjiffies, in_interrupt() ? 1 : 0,
			     current->pid, smp_processor_id(), current->comm);
	printk("\n**************key3_workqueu_end*****************\n");

}
/*
 * irq handled
 */

static irqreturn_t IO_irq_interrupt1(int irq, void *dev_id, struct pt_regs *regs)
{
	unsigned long j =0;
	j = jiffies;
	printk("\n**************KEY = 1*****************\n");
	if (!prevkey) printk("NO prevkey\nnow jiffies =0x%08lx\ncount = %d\n", j , ++key1.count);
	else if (!key1.prevjiffies) printk("prevkey=%d at 0x%08lx\nNO prekey1\nnow jiffies =0x%08lx\ncount = %d\n" , prevkey , prevjiffies , j, ++key1.count);
	else printk("prevkey=%d at  0x%08lx\nprekey1 at 0x%08lx\nnow jiffies =0x%08lx\ncount = %d\n" , prevkey , prevjiffies , key1.prevjiffies , j, ++key1.count);
	prevkey = 1;
	prevjiffies = key1.prevjiffies =j;
	tasklet_schedule(&keytask);
	printk("\n**************KEY = 1 END*****************\n");

	return IRQ_HANDLED;
}

static irqreturn_t IO_irq_interrupt2(int irq, void *dev_id, struct pt_regs *regs)
{
	unsigned long j =0;
	int result = 0;
	j = jiffies;

	printk("\n**************KEY = 2*****************\n");
	if (!prevkey) printk("NO prevkey\nnow jiffies =0x%08lx\ncount = %d\n", j , ++key2.count);
	else if (!key2.prevjiffies) printk("prevkey=%d at 0x%08lx\nNO prekey2\nnow jiffies =0x%08lx\ncount = %d\n" , prevkey , prevjiffies , j, ++key2.count);
	else printk("prevkey=%d at  0x%08lx\nprekey2 at 0x%08lx\nnow jiffies =0x%08lx\ncount = %d\n" , prevkey , prevjiffies , key2.prevjiffies , j, ++key2.count);
	prevkey = 2;
	prevjiffies = key2.prevjiffies =j;

	if ((result =queue_delayed_work(tekkamanwork , &irq_work_delay, DELAY))!=1) 	
	printk("IO_irq_interrupt2 cannot add work ! ");
	
	printk("result = %d ",result);

	printk("\n**************KEY = 2 END*****************\n");
	return IRQ_HANDLED;
}
static irqreturn_t IO_irq_interrupt3(int irq, void *dev_id, struct pt_regs *regs)
{
	unsigned long j =0;
	int result = 0;
	j = jiffies;
	printk("\n**************KEY = 3*****************\n");
	if (!prevkey) printk("NO prevkey\nnow jiffies =0x%08lx\ncount = %d\n", j , ++key3.count);
	else if (!key3.prevjiffies) printk("prevkey=%d at 0x%08lx\nNO prekey3\nnow jiffies =0x%08lx\ncount = %d\n" , prevkey , prevjiffies , j, ++key3.count);
	else printk("prevkey=%d at  0x%08lx\nprekey3 at 0x%08lx\nnow jiffies =0x%08lx\ncount = %d\n" , prevkey , prevjiffies , key3.prevjiffies , j, ++key3.count);
	prevkey = 3;
	prevjiffies = key3.prevjiffies =j;

	if ((result = schedule_work(&irq_work))!=1) printk("IO_irq_interrupt3 cannot add  work sharequeue  ! ");	
	printk("result = %d ",result);

	printk("\n**************KEY = 3 END*****************\n");
	return IRQ_HANDLED;
}
static irqreturn_t IO_irq_interrupt4(int irq, void *dev_id, struct pt_regs *regs)
{

	unsigned int len = 0;
	unsigned long j =0;
	j = jiffies;

	if (!prevkey) len = sprintf(tekkamanbuf,"\n**********KEY = 4**********\nNO prevkey\nnow jiffies =0x%08lx\ncount = %d\n*********KEY = 4 END***********\n", j , ++key4.count);
	else if (!key4.prevjiffies) len = sprintf(tekkamanbuf,"\n**********KEY = 4**********\nprevkey=%d at 0x%08lx\nNO prekey4\nnow jiffies =0x%08lx\ncount = %d\n*********KEY = 4 END***********\n" , prevkey , prevjiffies , j, ++key4.count);
	else len = sprintf(tekkamanbuf,"\n**********KEY = 4**********\nprevkey=%d at  0x%08lx\nprekey4 at 0x%08lx\nnow jiffies =0x%08lx\ncount = %d\n*********KEY = 4 END***********\n" , prevkey , prevjiffies , key4.prevjiffies , j, ++key4.count);

	prevkey = 4;
	prevjiffies = key4.prevjiffies =j;
	
	kfifo_put(tekkamanfifo, tekkamanbuf,  len);
	
	return IRQ_HANDLED;
}



/*
 * Open and close
 */


int IO_irq_open(struct inode *inode, struct file *filp)
{
	struct IO_irq_dev *dev; /* device information */
	int result1,result2,result3,result4;

	spin_lock(&IO_irq_lock);

	while (! atomic_dec_and_test (&IO_irq_available)) {
		atomic_inc(&IO_irq_available);
		spin_unlock(&IO_irq_lock);
		if (filp->f_flags & O_NONBLOCK) return -EAGAIN;
		if (wait_event_interruptible (IO_irq_wait, atomic_read (&IO_irq_available)))
			return -ERESTARTSYS; /* tell the fs layer to handle it */
		
		spin_lock(&IO_irq_lock);
	}

	spin_unlock(&IO_irq_lock);

	dev = container_of(inode->i_cdev, struct IO_irq_dev, cdev);

	if ((dev->IO_irq1 >= IRQ_EINT0)&&(dev->IO_irq2 >= IRQ_EINT0)&&(dev->IO_irq3 >= IRQ_EINT0)&&(dev->IO_irq4 >= IRQ_EINT0))
	{
		result1 = request_irq(dev->IO_irq1, IO_irq_interrupt1, 0 , "IO_irq1", (void *)NULL);
		result2 = request_irq(dev->IO_irq2, IO_irq_interrupt2, 0 , "IO_irq2", (void *)NULL);
		result3 = request_irq(dev->IO_irq3, IO_irq_interrupt3, 0 , "IO_irq3", (void *)NULL);
		result4 = request_irq(dev->IO_irq4, IO_irq_interrupt4, 0 , "IO_irq4", (void *)NULL);
		if (result1 || result2 || result3 || result4 ) {
			printk( "IO_irq: can't get assigned one of irq \n");
			if (!result1) free_irq(IO_irq_devices->IO_irq1, NULL);
			if (!result2) free_irq(IO_irq_devices->IO_irq2, NULL);
			if (!result3) free_irq(IO_irq_devices->IO_irq3, NULL);
			if (!result4) free_irq(IO_irq_devices->IO_irq4, NULL);
			return -EAGAIN;
		} else { 
			s3c2410_gpio_cfgpin(S3C2410_GPG11, S3C2410_GPG11_EINT19);
			s3c2410_gpio_pullup(S3C2410_GPG11, 0);
			s3c2410_gpio_cfgpin(S3C2410_GPG3, S3C2410_GPG3_EINT11);
			s3c2410_gpio_pullup(S3C2410_GPG3, 0);
			s3c2410_gpio_cfgpin(S3C2410_GPF2, S3C2410_GPF2_EINT2);
			s3c2410_gpio_pullup(S3C2410_GPF2, 0);
			s3c2410_gpio_cfgpin(S3C2410_GPF0, S3C2410_GPF0_EINT0);
			s3c2410_gpio_pullup(S3C2410_GPF0, 0);
			dev->IO_status = 0x1f ;
		}
	}
	else   {
		printk("IO_irq: get IRQ failed !\n");
		return -EAGAIN;
		}

	filp->private_data = dev; /* for other methods */

	tasklet_init(&keytask , key1_tasklet , (unsigned long)&key1);

	tekkamanwork = create_workqueue("tekkamanwork");
	INIT_DELAYED_WORK(&irq_work_delay, irq_work_delay_fn); 

	INIT_WORK(&irq_work, irq_work_fn);

	tekkamantmp = kmalloc(512, GFP_KERNEL);
	tekkamanbuf = kmalloc (512 , GFP_KERNEL);
	tekkamanfifo = kfifo_alloc(512, GFP_KERNEL, &tekkamanlock);
	
	printk( "IO_irq: opened !  \n");
	return nonseekable_open(inode, filp);          /* success */
}

int IO_irq_release(struct inode *inode, struct file *filp)
{
	
	free_irq(IO_irq_devices->IO_irq1, NULL);
	free_irq(IO_irq_devices->IO_irq2, NULL);
	free_irq(IO_irq_devices->IO_irq3, NULL);
	free_irq(IO_irq_devices->IO_irq4, NULL);
	s3c2410_gpio_cfgpin(S3C2410_GPG11, S3C2410_GPG11_INP);
	s3c2410_gpio_cfgpin(S3C2410_GPG3, S3C2410_GPG3_INP);
	s3c2410_gpio_cfgpin(S3C2410_GPF2, S3C2410_GPF2_INP);
	s3c2410_gpio_cfgpin(S3C2410_GPF0, S3C2410_GPF0_INP);
	IO_irq_devices->IO_status = 0 ;

	tasklet_kill(&keytask);

	if(!cancel_delayed_work(&irq_work_delay)) flush_workqueue(tekkamanwork);
	destroy_workqueue(tekkamanwork);
	
	kfifo_free(tekkamanfifo);
	kfree(tekkamantmp);
	kfree(tekkamanbuf);
	atomic_inc(&IO_irq_available); /* release the device */
	wake_up_interruptible_sync(&IO_irq_wait); /* awake other uid's */
	printk( "IO_irq: release !  \n");

	return 0;
}

/*
 * The ioctl() implementation
 */


ssize_t IO_irq_read(struct file *filp, char __user *buf, size_t count,
                loff_t *f_pos)
{
	struct IO_irq_dev *dev = filp->private_data; 
	ssize_t retval = 0;


	if (down_interruptible(&dev->sem))
		return -ERESTARTSYS;

	if (count > kfifo_len(tekkamanfifo))
		count = kfifo_len(tekkamanfifo);
	count = kfifo_get(tekkamanfifo,tekkamantmp, count);

	if (copy_to_user(buf, tekkamantmp, count)) {
		retval = -EFAULT;
		goto out;
	}
	retval = count;

  out:
	up(&dev->sem);
	return retval;
}

#if 1
int IO_irq_ioctl(struct inode *inode, struct file *filp,
                 unsigned int cmd, unsigned long arg)
{

	//int err = 0;
	int retval = 0;
	//unsigned int current_status;
  	/*
	 * extract the type and number bitfields, and don't decode
	 * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
	 */
	if (_IOC_TYPE(cmd) != IO_IRQ_MAGIC) return -ENOTTY;
	if (_IOC_NR(cmd) > IO_IRQ_MAXNR) return -ENOTTY;

	/*
	 * the direction is a bitmask, and VERIFY_WRITE catches R/W
	 * transfers. `Type' is user-oriented, while
	 * access_ok is kernel-oriented, so the concept of "read" and
	 * "write" is reversed
	 */
	switch(cmd) {
	  case IO_IRQ_1:
	if (IO_irq_devices->IO_status&IO_IRQ1_MASK) {
					disable_irq(IO_IRQ1);
					IO_irq_devices->IO_status &= ~IO_IRQ1_MASK;
				printk(KERN_NOTICE "disable_irq IO_IRQ_1 !\n");
				}
		else 		{
					enable_irq(IO_IRQ1);
					IO_irq_devices->IO_status |= IO_IRQ1_MASK;
				printk(KERN_NOTICE "enable_irq IO_IRQ_1 !\n");
				}
	break;
        
	  case IO_IRQ_2: /* Set: arg points to the value */
	if (IO_irq_devices->IO_status&IO_IRQ2_MASK) {
					disable_irq(IO_IRQ2);
					IO_irq_devices->IO_status &= ~IO_IRQ2_MASK;
				printk(KERN_NOTICE "disable_irq IO_IRQ_2 !\n");
				}
		else 		{
					enable_irq(IO_IRQ2);
					IO_irq_devices->IO_status |= IO_IRQ2_MASK;
				printk(KERN_NOTICE "enable_irq IO_IRQ_2 !\n");
				}
	break;

	  case IO_IRQ_3: /* Tell: arg is the value */
	if (IO_irq_devices->IO_status&IO_IRQ3_MASK) {
					disable_irq(IO_IRQ3);
					IO_irq_devices->IO_status &= ~IO_IRQ3_MASK;
				printk(KERN_NOTICE "disable_irq IO_IRQ_3 !\n");
				}
		else 		{
					enable_irq(IO_IRQ3);
					IO_irq_devices->IO_status |= IO_IRQ3_MASK;
				printk(KERN_NOTICE "enable_irq IO_IRQ_3 !\n");
				}
	break;

	  case IO_IRQ_4: /* Get: arg is pointer to result */
	if (IO_irq_devices->IO_status&IO_IRQ4_MASK) {
					disable_irq(IO_IRQ4);
					IO_irq_devices->IO_status &= ~IO_IRQ4_MASK;
				printk(KERN_NOTICE "disable_irq IO_IRQ_4 !\n");
				}
		else 		{
					enable_irq(IO_IRQ4);
					IO_irq_devices->IO_status |= IO_IRQ4_MASK;
				printk(KERN_NOTICE "enable_irq IO_IRQ_4 !\n");
				}
	break;

	case IO_IRQ_ALL: /* eXchange: use arg as pointer */
	if (IO_irq_devices->IO_status&IO_IRQ_ALL_MASK) {
					if (IO_irq_devices->IO_status&IO_IRQ1_MASK) 		disable_irq(IO_IRQ1);
					if (IO_irq_devices->IO_status&IO_IRQ2_MASK) 	disable_irq(IO_IRQ2);
					if (IO_irq_devices->IO_status&IO_IRQ3_MASK) 	disable_irq(IO_IRQ3);
					if (IO_irq_devices->IO_status&IO_IRQ4_MASK) 	disable_irq(IO_IRQ4);
					IO_irq_devices->IO_status &= ~IO_IRQ_ALL_MASK;
				printk(KERN_NOTICE "disable_irq IO_IRQ_ALL !\n");
				}
		else 		{
					if (IO_irq_devices->IO_status&IO_IRQ1_MASK) 		enable_irq(IO_IRQ1);
					if (IO_irq_devices->IO_status&IO_IRQ2_MASK) 	enable_irq(IO_IRQ2);
					if (IO_irq_devices->IO_status&IO_IRQ3_MASK) 	enable_irq(IO_IRQ3);
					if (IO_irq_devices->IO_status&IO_IRQ4_MASK) 	enable_irq(IO_IRQ4);
					IO_irq_devices->IO_status |= IO_IRQ_ALL_MASK;
				printk(KERN_NOTICE "enable_irq IO_IRQ_ALL !\n");
				}
	break;
	  case IO_KFIFO_SIZE:
		return (int) kfifo_len(tekkamanfifo);
		
	  case IO_KFIFO_RESET:
		kfifo_reset(tekkamanfifo);
		break;

	  default:  /* redundant, as cmd was checked against MAXNR */
		return -ENOTTY;
	}

	printk(KERN_NOTICE "IO_status= %x !\n",IO_irq_devices->IO_status);
	return retval;

}
#endif

struct file_operations IO_irq_fops = {
	.owner =   	THIS_MODULE,
	.ioctl =    	IO_irq_ioctl,
	.open =    	IO_irq_open,
	.release = 	IO_irq_release,
	.llseek =		no_llseek,
	.read =   	IO_irq_read,

};

/*
 * Finally, the module stuff
 */

/*
 * The cleanup function is used to handle initialization failures as well.
 * Thefore, it must be careful to work correctly even if some of the items
 * have not been initialized
 */
void IO_irq_cleanup_module(void)
{
	dev_t devno = MKDEV(IO_irq_major, IO_irq_minor);

	/* Get rid of our char dev entries */
	if (IO_irq_devices) {
		cdev_del(&IO_irq_devices->cdev);
		kfree(IO_irq_devices);
	}

	/* cleanup_module is never called if registering failed */
	unregister_chrdev_region(devno, 1);


}


/*
 * Set up the char_dev structure for this device.
 */
static void IO_irq_setup_cdev(struct IO_irq_dev *dev)
{
	int err, devno = MKDEV(IO_irq_major, IO_irq_minor);
    
	cdev_init(&dev->cdev, &IO_irq_fops);
	dev->cdev.owner = THIS_MODULE;
	err = cdev_add (&dev->cdev, devno, 1);
	/* Fail gracefully if need be */
	if (err)
		printk(KERN_NOTICE "Error %d adding IO_irq", err);
}


int IO_irq_init_module(void)
{
	int result;
	dev_t dev = 0;

/*
 * Get a range of minor numbers to work with, asking for a dynamic
 * major unless directed otherwise at load time.
 */
	if (IO_irq_major) {
		dev = MKDEV(IO_irq_major, IO_irq_minor);
		result = register_chrdev_region(dev, 1, "IO_irq");
	} else {
		result = alloc_chrdev_region(&dev, IO_irq_minor, 1,
				"IO_irq");
		IO_irq_major = MAJOR(dev);
	}
	if (result < 0) {
		printk(KERN_WARNING "IO_irq: can't get major %d\n", IO_irq_major);
		return result;
	}

        /* 
	 * allocate the devices -- we can't have them static, as the number
	 * can be specified at load time
	 */
	IO_irq_devices = kmalloc(sizeof(struct IO_irq_dev), GFP_KERNEL);
	if (!IO_irq_devices) {
		result = -ENOMEM;
		goto fail;  /* Make this more graceful */
	}
	memset(IO_irq_devices, 0, sizeof(struct IO_irq_dev));

        /* Initialize each device. */
		init_MUTEX(&IO_irq_devices->sem);
		IO_irq_devices->IO_irq1 = (unsigned int) IO_IRQ1;
		IO_irq_devices->IO_irq2 = (unsigned int) IO_IRQ2;
		IO_irq_devices->IO_irq3 = (unsigned int) IO_IRQ3;
		IO_irq_devices->IO_irq4 = (unsigned int) IO_IRQ4;
		IO_irq_devices->IO_status = 0 ;
		 IO_irq_setup_cdev(IO_irq_devices);

	set_irq_type(IO_IRQ1,IRQ_TYPE_EDGE_RISING );
	set_irq_type(IO_IRQ2,IRQ_TYPE_EDGE_FALLING );
	set_irq_type(IO_IRQ3,IRQ_TYPE_EDGE_RISING );
	set_irq_type(IO_IRQ4,IRQ_TYPE_EDGE_FALLING );
	return 0; /* succeed */

  fail:
	IO_irq_cleanup_module();
	return result;
}

module_init(IO_irq_init_module);
module_exit(IO_irq_cleanup_module);

MODULE_AUTHOR("Tekkaman Ninja");
MODULE_LICENSE("Dual BSD/GPL");
